﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Data.SQLite;

namespace KeyboardImageViewer
{
    /// <summary>
    /// Interaction logic for EditKeys.xaml
    /// </summary>
    public partial class EditKeys : Window
    {
        private List<EditKeyRow> keyRows;
        private int currentRow;
        private int currentActionRow;
        private TextBlock selectedBlock;

        private bool inCaptureMode;
        private Rectangle frostRectangle;
        private MouseButtonEventHandler mouseButtonHandler;
        private MouseWheelEventHandler mouseWheelHandler;
        private TextBlock captureHelpText;

        public EditKeys()
        {
            this.mouseButtonHandler = new MouseButtonEventHandler(HandleMouseClick);
            this.mouseWheelHandler = new MouseWheelEventHandler(HandleMouseWheel);

            InitializeComponent();

            ImageManager.Config.ApplyWindowSize("EditKeys", this);

            CommandManager.AddCanExecuteHandler(this, this.CanExecuteHandler);

            this.keyRows = new List<EditKeyRow>();

            this.AddKeysToGrid();
        }

        private void AddKeysToGrid()
        {
            this.selectedBlock = null;

            this.actionGrid.Children.Clear();
            this.actionGrid.RowDefinitions.Clear();

            this.actionGrid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });

            TextBlock actionHeader = new TextBlock {
                Text = "Action",
                Margin = new Thickness(1),
                Padding = new Thickness(2),
                FontSize = 14,
                FontWeight = FontWeights.Bold,
                Foreground = Brushes.DarkBlue };
            Grid.SetRow(actionHeader, 0);
            Grid.SetColumn(actionHeader, 0);
            this.actionGrid.Children.Add(actionHeader);

            TextBlock keyHeader = new TextBlock {
                Text = "Key/Button",
                Margin = new Thickness(1),
                Padding = new Thickness(2),
                FontSize = 14,
                FontWeight = FontWeights.Bold,
                Foreground = Brushes.DarkBlue };
            Grid.SetRow(keyHeader, 0);
            Grid.SetColumn(keyHeader, 1);
            this.actionGrid.Children.Add(keyHeader);

            this.currentActionRow = 0;
            this.currentRow = 1;
            this.AddUneditableAction("Show This Screen", "F1");
            this.AddUserAction(UserAction.ShowOptions);
            this.AddUserAction(UserAction.ReloadFilesFromDisk);
            this.AddUserAction(UserAction.ToggleFullscreen);
            this.AddUserAction(UserAction.Minimize);
            this.AddUserAction(UserAction.Quit);

            this.AddSpacer();

            this.AddUserAction(UserAction.RateAs1);
            this.AddUserAction(UserAction.RateAs2);
            this.AddUserAction(UserAction.RateAs3);
            this.AddUserAction(UserAction.RateAs4);
            this.AddUserAction(UserAction.RateAs5);
            this.AddUserAction(UserAction.ClearRating);
            this.AddUserAction(UserAction.Tag);
            this.AddUserAction(UserAction.TagEditMultiple);
            this.AddUserAction(UserAction.TagRenameOrDelete);
            this.AddUserAction(UserAction.Rename);
            this.AddUserAction(UserAction.Move);
            this.AddUserAction(UserAction.CopyFiles);
            this.AddUserAction(UserAction.EditCurrentFile);
            this.AddUserAction(UserAction.DeleteCurrentFile);
            this.AddUserAction(UserAction.Import);
            this.AddUserAction(UserAction.EditTagDependencies);

            this.AddSpacer();

            this.AddUserAction(UserAction.ShowPreviousImage);
            this.AddUserAction(UserAction.ShowNextImage);
            this.AddUserAction(UserAction.MoveToFirstImage);
            this.AddUserAction(UserAction.MoveToLastImage);
            this.AddUserAction(UserAction.PlayStopSlideshow);
            this.AddUserAction(UserAction.ShowLists);
            this.AddUserAction(UserAction.ChangeOrder);
            this.AddUserAction(UserAction.Search);
            this.AddUserAction(UserAction.ClearSearch);
            this.AddUserAction(UserAction.ShowRating1OrGreater);
            this.AddUserAction(UserAction.ShowRating2OrGreater);
            this.AddUserAction(UserAction.ShowRating3OrGreater);
            this.AddUserAction(UserAction.ShowRating4OrGreater);
            this.AddUserAction(UserAction.ShowRating5OrGreater);
            this.AddUserAction(UserAction.ClearRatingFilter);
            this.AddUserAction(UserAction.ShowOnlyUnrated);
            this.AddUserAction(UserAction.ShowOnlyUntagged);
            this.AddUserAction(UserAction.ShowDuplicates);
            this.AddUserAction(UserAction.ShowAllDuplicates);

            this.actionGrid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
        }

        private void AddUserAction(UserAction action)
        {
            this.actionGrid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });

            TextBlock actionBlock = new TextBlock {
                Text = InputMapping.GetFriendlyName(action) + ":",
                FontWeight = FontWeights.Bold,
                TextWrapping = TextWrapping.Wrap,
                Padding = new Thickness(2),
                Margin = new Thickness(1) };
            Grid.SetRow(actionBlock, this.currentRow);
            Grid.SetColumn(actionBlock, 0);
            this.actionGrid.Children.Add(actionBlock);

            int currentColumn = 1;

            EditKeyRow editKeyRow = new EditKeyRow { Action = action };

            SQLiteCommand selectAction = new SQLiteCommand("SELECT * FROM inputMapping WHERE actionCode = " + ((int)action), ImageManager.Connection);
            using (SQLiteDataReader reader = selectAction.ExecuteReader())
            {
                while (reader.Read())
                {
                    int inputCode = reader.GetInt32("inputCode");
                    InputType inputType = (InputType)reader.GetInt32("inputType");

                    InputCommand inputCommand = new InputCommand { InputCode = inputCode, InputType = inputType };

                    if (currentColumn < 4)
                    {
                        TextBlock keyBlock = new TextBlock {
                            Text = InputMapping.GetFriendlyName(inputCode, inputType),
                            Padding = new Thickness(2),
                            Margin = new Thickness(1) };
                        Grid.SetRow(keyBlock, this.currentRow);
                        Grid.SetColumn(keyBlock, currentColumn);
                        keyBlock.Tag = inputCommand;
                        this.actionGrid.Children.Add(keyBlock);
                        keyBlock.MouseLeftButtonDown += new MouseButtonEventHandler(this.textBlockMouseDown);
                        keyBlock.MouseEnter += new MouseEventHandler(this.textBlockMouseEnter);
                        keyBlock.MouseLeave += new MouseEventHandler(this.textBlockMouseLeave);
                        editKeyRow.KeyBlocks.Add(keyBlock);
                    }

                    currentColumn++;
                }
            }

            // Fill in unused blocks with blank TextBlocks
            while (currentColumn < 4)
            {
                TextBlock blankBlock = new TextBlock { Text = "", Padding = new Thickness(2), Margin = new Thickness(1) };
                Grid.SetRow(blankBlock, this.currentRow);
                Grid.SetColumn(blankBlock, currentColumn);
                this.actionGrid.Children.Add(blankBlock);
                blankBlock.MouseLeftButtonDown += new MouseButtonEventHandler(this.textBlockMouseDown);
                blankBlock.MouseEnter += new MouseEventHandler(this.textBlockMouseEnter);
                blankBlock.MouseLeave += new MouseEventHandler(this.textBlockMouseLeave);
                editKeyRow.KeyBlocks.Add(blankBlock);

                currentColumn++;
            }

            // On alternate rows, add a light gray background
            if (this.currentActionRow % 2 == 0)
            {
                Rectangle backgroundRectangle = new Rectangle { Fill = new SolidColorBrush(Color.FromRgb(250, 250, 250)) };
                Grid.SetRow(backgroundRectangle, this.currentRow);
                Grid.SetColumn(backgroundRectangle, 0);
                Grid.SetColumnSpan(backgroundRectangle, 4);
                Canvas.SetZIndex(backgroundRectangle, -1);
                this.actionGrid.Children.Add(backgroundRectangle);
            }

            this.currentRow++;
            this.currentActionRow++;

            this.keyRows.Add(editKeyRow);
        }

        private void AddUneditableAction(string actionName, params string[] keys)
        {
            this.actionGrid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });

            TextBlock actionBlock = new TextBlock { Text = actionName + ":", FontWeight = FontWeights.Bold, Padding = new Thickness(2), Margin = new Thickness(1) };
            Grid.SetRow(actionBlock, this.currentRow);
            Grid.SetColumn(actionBlock, 0);
            this.actionGrid.Children.Add(actionBlock);

            for (int i = 0; i < keys.Length && i < 3; i++)
            {
                TextBlock keyBlock = new TextBlock { Text = keys[i], Padding = new Thickness(2), Margin = new Thickness(1), Foreground = Brushes.DarkGray };
                Grid.SetRow(keyBlock, this.currentRow);
                Grid.SetColumn(keyBlock, i + 1);
                this.actionGrid.Children.Add(keyBlock);
            }

            // On alternate rows, add a light gray background
            if (this.currentActionRow % 2 == 0)
            {
                Rectangle backgroundRectangle = new Rectangle { Fill = new SolidColorBrush(Color.FromRgb(250, 250, 250)) };
                Grid.SetRow(backgroundRectangle, this.currentRow);
                Grid.SetColumn(backgroundRectangle, 0);
                Grid.SetColumnSpan(backgroundRectangle, 4);
                Canvas.SetZIndex(backgroundRectangle, -1);
                this.actionGrid.Children.Add(backgroundRectangle);
            }

            this.currentRow++;
            this.currentActionRow++;
        }

        private void AddSpacer()
        {
            RowDefinition spacerRowDef = new RowDefinition();
            spacerRowDef.Height = new GridLength(25);
            this.actionGrid.RowDefinitions.Add(spacerRowDef);

            this.currentRow++;
        }

        private void textBlockMouseDown(object sender, MouseButtonEventArgs e)
        {
            TextBlock block = (TextBlock)sender;
            if (e.ClickCount == 1)
            {
                if (this.selectedBlock != null)
                {
                    this.selectedBlock.Background = null;
                }

                block.Background = SharedUI.SelectColor;
                this.selectedBlock = block;
            }

            if (e.ClickCount == 2)
            {
                this.PromptForNewKey();
            }
        }

        private void PromptForNewKey()
        {
            this.inCaptureMode = true;
            this.frostRectangle = new Rectangle { Opacity = .80, Fill = Brushes.White };
            this.mainGrid.Children.Add(frostRectangle);

            this.captureHelpText = new TextBlock
            {
                Text = "Press a key or mouse button\n(Shift + Esc to cancel)",
                HorizontalAlignment = HorizontalAlignment.Center,
                VerticalAlignment = VerticalAlignment.Center,
                FontSize = 24,
                TextAlignment = TextAlignment.Center
            };
            this.mainGrid.Children.Add(captureHelpText);

            this.MouseDown += this.mouseButtonHandler;
            this.MouseWheel += this.mouseWheelHandler;
        }

        private void HandleKeyPress(object sender, KeyEventArgs e)
        {
            if (this.inCaptureMode)
            {
                if (e.Key == Key.Escape && (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift)))
                {
                    this.EndKeyCaptureMode();
                    e.Handled = true;
                    return;
                }

                if (e.Key == Key.System)
                {
                    if (e.SystemKey != Key.LeftAlt && e.SystemKey != Key.RightAlt && e.SystemKey != Key.Tab && e.SystemKey != Key.Space)
                    {
                        this.SetNewKey((int)e.SystemKey, InputType.KeyboardAlt);
                        this.EndKeyCaptureMode();
                    }
                    
                    e.Handled = true;
                    return;
                }

                if (e.Key == Key.LeftCtrl || e.Key == Key.RightCtrl || e.Key == Key.LeftShift || e.Key == Key.RightShift || e.Key == Key.F1)
                {
                    e.Handled = true;
                    return;
                }

                if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl))
                {
                    this.SetNewKey((int)e.Key, InputType.KeyboardCtrl);
                }
                else if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift))
                {
                    this.SetNewKey((int)e.Key, InputType.KeyboardShift);
                }
                else
                {
                    this.SetNewKey((int)e.Key, InputType.Keyboard);
                }

                this.EndKeyCaptureMode();

                e.Handled = true;
            }
        }

        private void HandleMouseClick(object sender, MouseButtonEventArgs e)
        {
            this.SetNewKey((int)e.ChangedButton, InputType.Mouse);

            this.EndKeyCaptureMode();

            e.Handled = true;
        }

        private void HandleMouseWheel(object sender, MouseWheelEventArgs e)
        {
            int clicks = e.Delta / -120;

            if (clicks > 0)
            {
                this.SetNewKey((int)MouseWheelAction.Down, InputType.MouseWheel);
            }
            else if (clicks < 0)
            {
                this.SetNewKey((int)MouseWheelAction.Up, InputType.MouseWheel);
            }

            this.EndKeyCaptureMode();

            e.Handled = true;
        }

        private void CanExecuteHandler(object sender, CanExecuteRoutedEventArgs e)
        {
            if (this.inCaptureMode)
            {
                e.Handled = true;
                e.CanExecute = false;
            }

        }

        private void SetNewKey(int inputCode, InputType inputType)
        {
            InputCommand inputCommand = new InputCommand { InputCode = inputCode, InputType = inputType };

            bool ourRow = false;
            TextBlock targetBlock = null;
            UserAction oldAction = UserAction.None;

            // See if this key is already being used.
            foreach (EditKeyRow keyRow in this.keyRows)
            {
                ourRow = false;

                foreach (TextBlock textBlock in keyRow.KeyBlocks)
                {
                    if (textBlock == this.selectedBlock)
                    {
                        ourRow = true;
                    }

                    if (textBlock.Tag != null && inputCommand.Equals((InputCommand)textBlock.Tag))
                    {
                        targetBlock = textBlock;
                        oldAction = keyRow.Action;
                    }
                }

                if (targetBlock != null)
                {
                    break;
                }
            }

            if (targetBlock != null)
            {
                if (ourRow)
                {
                    // If the old row is our row, just delete the old key.
                    targetBlock.Text = "";
                    targetBlock.Tag = null;
                }
                else
                {
                    if (MessageBox.Show(InputMapping.GetFriendlyName(inputCode, inputType) + " is currently assigned to " + InputMapping.GetFriendlyName(oldAction) + ". Do you want to reassign the key?", "Confirm Key Reassignment", MessageBoxButton.YesNo) == MessageBoxResult.No)
                    {
                        return;
                    }

                    targetBlock.Text = "";
                    targetBlock.Tag = null;
                }
            }

            this.selectedBlock.Text = InputMapping.GetFriendlyName(inputCode, inputType);
            this.selectedBlock.Tag = inputCommand;
        }

        private void EndKeyCaptureMode()
        {
            this.inCaptureMode = false;

            this.mainGrid.Children.Remove(this.frostRectangle);
            this.mainGrid.Children.Remove(this.captureHelpText);

            this.MouseDown -= this.mouseButtonHandler;
            this.MouseWheel -= this.mouseWheelHandler;
        }

        private void textBlockMouseEnter(object sender, MouseEventArgs e)
        {
            TextBlock block = (TextBlock)sender;

            if (block != this.selectedBlock)
            {
                block.Background = SharedUI.HoverColor;
            }
        }

        private void textBlockMouseLeave(object sender, MouseEventArgs e)
        {
            TextBlock block = (TextBlock)sender;

            if (block != this.selectedBlock)
            {
                block.Background = null;
            }
        }

        private void resetButton_Click(object sender, RoutedEventArgs e)
        {
            if (MessageBox.Show("Do you really want to set all controls back to defaults?", "Confirm Reset", MessageBoxButton.YesNo) == MessageBoxResult.Yes)
            {
                InputMapping.ResetToDefaults(ImageManager.Connection);

                this.AddKeysToGrid();
            }
        }

        private void okButton_Click(object sender, RoutedEventArgs e)
        {
            using (SQLiteTransaction transaction = ImageManager.Connection.BeginTransaction())
            {
                foreach (EditKeyRow keyRow in this.keyRows)
                {
                    Database.ExecuteNonQuery("DELETE FROM inputMapping WHERE actionCode = " + ((int)keyRow.Action), ImageManager.Connection);

                    foreach (TextBlock textBlock in keyRow.KeyBlocks)
                    {
                        if (textBlock.Tag != null)
                        {
                            InputCommand inputCommand = (InputCommand)textBlock.Tag;

                            Database.ExecuteNonQuery("INSERT INTO inputMapping (inputCode, inputType, actionCode) VALUES (" + inputCommand.InputCode + ", " + ((int)inputCommand.InputType) + ", " + ((int)keyRow.Action) + ")", ImageManager.Connection);
                        }
                    }
                }

                transaction.Commit();
            }

            this.DialogResult = true;
        }

        private void Window_KeyDown(object sender, KeyEventArgs e)
        {
            if (!this.inCaptureMode)
            {
                if (e.Key == Key.Delete && this.selectedBlock != null && this.selectedBlock.Tag != null)
                {
                    if (MessageBox.Show("Are you sure you want to unbind this key?", "Confirm Key Unbind", MessageBoxButton.YesNo) == MessageBoxResult.Yes)
                    {
                        this.selectedBlock.Text = "";
                        this.selectedBlock.Tag = null;
                    }
                }
                else if (e.Key == Key.Enter && this.selectedBlock != null)
                {
                    this.PromptForNewKey();
                    e.Handled = true;
                }
            }
        }

        private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            ImageManager.Config.SaveWindowSize("EditKeys", this);
        }
    }
}
